package com.microtf.inscription.framework.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.microtf.inscription.framework.vo.net.PostFile;
import lombok.extern.slf4j.Slf4j;
import okhttp3.FormBody;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * 网络请求类
 *
 * @author guliuzhong
 */
@Slf4j
@Service
public class NetService {
    public static final String URL_START = "?";
    private static final String URL_SPLIT = "&";
    private static final String SCHEME_FILE = "file";
    ObjectMapper objectMapper;
    private final ThreadLocal<Request.Builder> builderThreadLocal = new ThreadLocal<>();
    private final ThreadLocal<Boolean> requireSuccessThreadLocal = new ThreadLocal<>();
    private OkHttpClient okHttpClient;

    @Autowired
    public void setOkHttpClient(OkHttpClient okHttpClient) {
        this.okHttpClient = okHttpClient;
    }

    @Autowired
    public void setObjectMapper(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    /**
     * 开始新请求
     *
     * @return NetClient
     */
    public NetService newRequest() {
        builderThreadLocal.set(new Request.Builder());
        requireSuccessThreadLocal.set(true);
        return this;
    }

    /**
     * 请求结束
     */
    private void endRequest() {
        builderThreadLocal.remove();
        requireSuccessThreadLocal.remove();
    }

    /**
     * 向服务器发送post请求
     * 与 setJson,setBin,setPostFile互折
     *
     * @param postMap post的数据key,value map
     * @return 当前对象自身，支持链式调用
     */
    public NetService setPostMap(Map<String, String> postMap) {
        if (postMap.size() > 0) {
            FormBody.Builder builder1 = new FormBody.Builder();
            for (Map.Entry<String, String> postItem : postMap.entrySet()) {
                builder1.add(postItem.getKey(), postItem.getValue());
            }
            RequestBody build = builder1.build();
            Request.Builder builder = builderThreadLocal.get();
            builder.post(build);
        }
        return this;
    }

    /**
     * 向服务器发送post文件
     * 与 setJson,setBin,setPostMap互折
     *
     * @param postMap post的数据key,value map
     * @param postFile post上传文件
     * @return 当前对象自身，支持链式调用
     */
    public NetService setPostFile(Map<String, String> postMap, Map<String, PostFile> postFile) throws IOException {
        MultipartBody.Builder multiPartBodyBuilder = new MultipartBody.Builder();
        multiPartBodyBuilder.setType(MultipartBody.FORM);
        if (postMap != null && postMap.size() > 0) {
            for (Map.Entry<String, String> postItem : postMap.entrySet()) {
                multiPartBodyBuilder.addFormDataPart(postItem.getKey(), postItem.getValue());
            }
        }
        if (postFile != null && postFile.size() > 0) {
            for (Map.Entry<String, PostFile> postItem : postFile.entrySet()) {
                byte[] bytes;
                PostFile value = postItem.getValue();
                if (value.getContent() != null) {
                    bytes = value.getContent();
                } else {
                    if (SCHEME_FILE.equalsIgnoreCase(value.getUri().getScheme())) {
                        bytes = Files.readAllBytes(Paths.get(value.getUri().getPath()));
                    } else {
                        Request.Builder postRequestBuilder = new Request.Builder();
                        Response execute = okHttpClient.newCall(postRequestBuilder.url(value.getUri().toString()).build()).execute();
                        assert execute.body() != null;
                        bytes = execute.body().bytes();
                    }
                }

                RequestBody requestBody = RequestBody.create(MediaType.parse(value.getContentType()), bytes);
                multiPartBodyBuilder.addFormDataPart(postItem.getKey(), value.getFileName() != null ? value.getFileName() : value.getUri().getPath(), requestBody);
            }
        }
        Request.Builder builder = builderThreadLocal.get();
        builder.post(multiPartBodyBuilder.build());
        return this;
    }

    /**
     * 设置header头
     *
     * @param headerMap header 头
     * @return 当前对象自身，支持链式调用
     */
    public NetService setHeaderMap(Map<String, String> headerMap) {
        if (headerMap.size() > 0) {
            Request.Builder builder = builderThreadLocal.get();
            for (Map.Entry<String, String> postItem : headerMap.entrySet()) {
                builder.addHeader(postItem.getKey(), postItem.getValue());
            }
        }
        return this;
    }

    /**
     * 向服务器发送json数据
     * 与 setJson,setBin,setPostMap,setPostFile互折
     *
     * @param json json文本格式数据
     * @return 当前对象自身，支持链式调用
     */
    public NetService setJson(String json) {
        Request.Builder builder = builderThreadLocal.get();
        builder.post(RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json));
        return this;
    }

    /**
     * 向服务器发送json数据
     * <p>与 setJson,setBin,setPostMap,setPostFile互折</p>
     * 自动使用jackson进行序列化。
     *
     * @param jsonObject json数据对象
     * @return 当前对象自身，支持链式调用
     */
    public NetService setJson(Object jsonObject) throws JsonProcessingException {
        return setJson(objectMapper.writeValueAsString(jsonObject));
    }

    /**
     * 向服务器POST发送其它数据格式
     * 与 setJson,setPostMap,setPostFile互折
     *
     * @param contextType mime type
     * @param bytes       数据内容
     * @return 当前对象自身，支持链式调用
     */
    public NetService setBin(String contextType, byte[] bytes) {
        Request.Builder builder = builderThreadLocal.get();
        builder.post(RequestBody.create(MediaType.parse(contextType), bytes));
        return this;
    }

    private String urlEncode(String encodeString) {
        return URLEncoder.encode(encodeString, StandardCharsets.UTF_8);
    }

    /**
     * 设置url
     * 如果不调用 setJson,setBin,setPostMap,setPostFile 使用get发送。
     *
     * @param url url 地址
     * @return 当前对象自身，支持链式调用
     */
    public NetService setUrl(URL url) {
        return setUrl(url.toString(), null);
    }

    /**
     * 设置url
     * 如果不调用 setJson,setBin,setPostMap,setPostFile 使用get发送。
     *
     * @param url     url 地址
     * @param queries get的map格式数据参数
     * @return 当前对象自身，支持链式调用
     */
    public NetService setUrl(URL url, Map<String, String> queries) {
        return setUrl(url.toString(), queries);
    }

    /**
     * 设置url
     * 如果不调用 setJson,setBin,setPostMap,setPostFile 使用get发送。
     *
     * @param url url 地址
     * @return 当前对象自身，支持链式调用
     */
    public NetService setUrl(String url) {
        return setUrl(url, null);
    }

    /**
     * 设置url
     * 如果不调用 setJson,setBin,setPostMap,setPostFile 使用get发送。
     *
     * @param url     url 地址
     * @param queries get的map参数
     * @return 当前对象自身，支持链式调用
     */
    public NetService setUrl(String url, Map<String, String> queries) {
        StringBuilder sb = new StringBuilder();
        if (queries != null && queries.size() > 0) {
            for (Map.Entry<String, String> next : queries.entrySet()) {
                String key = next.getKey();
                Pattern pattern = Pattern.compile("\\{" + key + "}");
                if (pattern.matcher(url).find()) {
                    url = url.replaceAll(pattern.toString(), urlEncode(next.getValue()));
                } else {
                    sb.append(next.getKey());
                    sb.append("=");
                    sb.append(urlEncode(next.getValue()));
                    sb.append("&");
                }

            }
        }
        if (sb.length() > 0) {
            if (url.contains(URL_START)) {
                url = url + URL_SPLIT + sb.substring(0, sb.length() - 1);
            } else {
                url = url + URL_START + sb.substring(0, sb.length() - 1);
            }
        }
        Request.Builder builder = builderThreadLocal.get();
        builder.url(url);
        return this;
    }

    /**
     * 设置http状态不为200到300时返回内容处理
     * <p>
     * 不调用为true
     *
     * @param reqSuccess true 时http code必须为200到300之间才设置返回内容， false时 都设置
     * @return 当前对象自身，支持链式调用
     */
    public NetService setReqSuccess(Boolean reqSuccess) {
        requireSuccessThreadLocal.set(reqSuccess);
        return this;
    }

    /**
     * 返回http的二进制数据
     *
     * @return byte
     */
    public byte[] requestToBytes() {
        Response execute;
        try {
            Request.Builder builder = builderThreadLocal.get();
            execute = okHttpClient.newCall(builder.build()).execute();
            Boolean requireSuccess = requireSuccessThreadLocal.get();
            assert execute.body() != null;
            if (requireSuccess && !execute.isSuccessful()) {
                log.error("http return httpCode {},BODY {}", execute.code(), execute.body().string());
                return null;
            }
            return execute.body().bytes();
        } catch (IOException e) {
            log.error("request http error {}", e.getMessage());
        } finally {
            endRequest();
        }
        return null;
    }

    /**
     * 返回http内容字符串
     *
     * @return string
     */
    public String requestToString() {
        byte[] bytes = requestToBytes();
        if (bytes == null) {
            return null;
        }
        return new String(bytes, StandardCharsets.UTF_8);
    }

    /**
     * 将http的结果转换成class实体
     *
     * @param classes json反序列化的class
     * @return T
     * @author guliuzhong
     */
    public <T> T requestToObject(Class<T> classes) {
        String s = requestToString();
        if (s != null) {
            T t = null;
            try {
                t = objectMapper.readValue(s, classes);
            } catch (IOException e) {
                log.error("http return object error{}", e.getMessage());
            }
            return t;
        }
        return null;
    }
}
